<template>
  <div class="terminal" @click="runWindowInput">
    <div
      class="termResult"
      v-html="termResult"
    />
    <div class="commandLine">
      <div style="font-weight:bold;" v-html="frontDisplay" />
      <el-input
        ref="termInput"
        v-model="termWrite"
        type="textarea"
        resize="none"
        :disabled="isloading"
        autosize
        autofocus
        @keydown.enter.native="handleCommand(null)"
        @keydown.tab.native="tabInput($event)"
        @keydown.up.native="getForword"
        @keydown.down.native="getNext"
        @blur="isBlur"
      />
    </div>
  </div>
</template>

<script>
import HelpInfo from '@/helpInfo.js';
export default {
  name: 'RunWindow',
  props: {
    variables: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      termResult: '', // 命令行展示
      termWrite: '', // 命令行输入
      commands: [], // 命令行命令
      isloading: false,
      oldRange: {},
      saveCode: '',
      frontDisplay: 'Tianyan&nbsp;&gt;&nbsp;',
      currentIndent: [{ type: 7, Indent: 0, isTryType: false }],
      Indent: 0,
      tags: {
        isHandle: false,
        isIndent: false
      }
    };
  },
  watch: {
    termResult(newVal, oldVal) { // 自动聚焦到底部
      this.$nextTick(() => {
        this.$refs.termInput.focus();
      });
    },
    termWrite(newVal) {
      if (newVal.includes('\n')) {
        this.handleMultCommand(this.termWrite);
      }
    }
  },
  created() {
    this.initTerminal();
    this.addCommand({
      name: 'clear',
      describe: 'Clear the Tianyan Workspace',
      func: () => {
        this.$store.commit('matlab/clearVariables');
      }
    });
    this.addCommand({
      name: 'clc',
      describe: 'Clear the Tianyan Terminal',
      func: () => {
        this.termResult = '';
      }
    });
  },
  methods: {
    runWindowInput() { // 点击命令行区域即进入输入
      const range = window.getSelection().getRangeAt(0);
      if (window.getSelection().toString() === '') {
        this.$refs.termInput.focus();
      } else {
        if (!this.oldRange ||
            (this.oldRange.startContainer === range.startContainer &&
            this.oldRange.endContainer === range.endContainer &&
            this.oldRange.startOffset === range.startOffset &&
            this.oldRange.endOffset === range.endOffset)) {
          this.$refs.termInput.focus();
        }
      }
      this.oldRange = range;
    },
    isBlur() {
      var len = this.$refs.termInput.value.length;
      var dom = this.$refs.termInput.$el.querySelector('textarea');
      dom.setSelectionRange(len, len);
    },
    initTerminal() {
      this.termResult = HelpInfo.text; // 初始化命令行说明
      this.$store.commit('matlab/setTerm', (str) => { // 设置matlab.js中命令行输出方法
        this.termResult += str;
        this.termResult += '\n';
      });
      this.$store.commit('matlab/setTermCall', (str) => { // 设置matlab.js中命令行调用方法
        this.handleCommand(str);
      });
      this.saveCode = '';
    },
    addCommand(command) {
      this.commands.push(command);
    },
    replace(str) {
      str = str.replace(/</g, '\&lt');
      str = str.replace(/>/g, '\&gt');
      return str;
    },
    tabInput(e) {
      e.preventDefault();
      const insertText = '  '; // 两个空格
      const elInput = e.target;
      const startPos = elInput.selectionStart;
      const endPos = elInput.selectionEnd;
      if (startPos === undefined || endPos === undefined) return;
      const txt = elInput.value;
      elInput.value = txt.substring(0, startPos) + insertText + txt.substring(endPos);
      elInput.focus();
      elInput.selectionStart = startPos + insertText.length;
      elInput.selectionEnd = startPos + insertText.length;
      this.termWrite = elInput.value;
    },
    recordInput(str) { // 记录上一次输入并展示在命令行里
      this.termResult += `<span style="font-weight:bold;">${this.frontDisplay}</span>`;
      this.termResult += `<span style="color:#3CB371;">${this.replace(str || '')}</span>`;
      this.termResult += '\n';
    },
    getForword(e) { // 上一条命令
      if (e.preventDefault) e.preventDefault();
      else window.event.value = false;
      this.$store.commit('matlab/moveTermArray', { payload: 'f_command', TermWrite: this.termWrite });
      this.termWrite = this.$store.getters['matlab/getTermWrite'];
      var dom1 = this.$refs.termInput.$el.querySelector('textarea');
      this.isloading = false;
      this.$nextTick(() => {
        dom1.focus();
        if (this.termWrite) dom1.setSelectionRange(this.termWrite.length, this.termWrite.length);
      });
    },
    getNext(e) { // 下一条命令
      if (e.preventDefault) e.preventDefault();
      else window.event.value = false;
      this.$store.commit('matlab/moveTermArray', { payload: 'n_command', TermWrite: this.termWrite });
      this.termWrite = this.$store.getters['matlab/getTermWrite'];
      var dom1 = this.$refs.termInput.$el.querySelector('textarea');
      this.isloading = false;
      this.$nextTick(() => {
        dom1.focus();
        if (this.termWrite) dom1.setSelectionRange(this.termWrite.length, this.termWrite.length);
      });
    },
    throwErr(errname, errtype) { // 缩进以及一些错误的显示
      this.termResult += `<span style="color:red">${this.replace(errname)}: ${this.replace(errtype)}</span>\n`;
    },
    findIndex(indent) {
      for (let i = this.currentIndent.length - 1; i >= 0; i--) {
        if (this.currentIndent[i].Indent === indent) return i;
      }
      return -1;
    },
    removeCurrentIndent(index) {
      for (let i = this.currentIndent.length - 1; i > index; i--) {
        this.currentIndent.pop();
      }
    },
    handleErr(TermWrite, errname, errtype) {
      this.followUpOperations(TermWrite, true); // true是将savecode置空
      this.tags.isHandle = false;
      this.throwErr(errname, errtype);
      this.removeCurrentIndent(0);
      this.Indent = 0;
      this.frontDisplay = 'Tianyan&nbsp;&gt;&nbsp;';
    },
    handleIndent(index, indent, type) {
      this.currentIndent[index].type = type;
      this.removeCurrentIndent(index);
      this.tags.isIndent = true;
      this.Indent = indent;
    },
    checkType(input) { // 检查当前缩进对应的类型
      const str = input.trim();
      if (/^(for|def|class)\s.*:/g.test(str)) return 0; // for def class的缩进类型，只要找到缩进就可以
      else if (/^if\s.*:/g.test(str)) return 1; // if缩进类型，后面可以覆盖elif和else，只要找到缩进就可以
      else if (/^while\s.*:/g.test(str)) return 2; // while类型，后面可以覆盖else，只要找到缩进就可以
      else if (/^try.*:/g.test(str)) return 3; // try类型，后面可以覆盖except和else，只要找到缩进就可以
      else if (/^elif\s.*:/g.test(str)) return 4; // elif类型，可以覆盖if，elif（1，4），被else覆盖
      else if (/^else.*:/g.test(str)) return 5; // else类型，可以覆盖if，elif，while，except（1，4，2，6），可被finally覆盖
      else if (/^except.*:/g.test(str)) return 6; // except，可以覆盖try，except（3，6）
      else if (/^finally.*:/g.test(str)) return 9; // finally，可以覆盖try，except，try中的else
      else if (/^(for|def|class|if|while|try|elif|else|except|finally)/g.test(str)) return 8;
      else return 7;
    },
    checkSentence(input) { // 解析输入，进行相应处理
      const str = input.replace(/^(\s)*/g, ''); // 处理缩进
      if (/^\w+(\s*[\\+-\\*/%&\\^\\|]?=\s*).*/g.test(str)) return 0; // 判断是否为赋值语句
      if (/^\w+(\s*(>>=)|(<<=)|(\*\*=)|(\/\/=)\s*).*/g.test(str)) return 0; // 判断是否为复合赋值语句
      if (/^print\(.*\).*/g.test(str)) return 0; // 判断是否为print语句
      if (/^return.*/g.test(str)) return 0; // 判断是否为return语句
      if (/^(for.*|def.*|if.*|while.*|class.*|try.*)/g.test(str)) return 0;
      if (/^pangu\.plot.?\..*/g.test(str)) return 0; // 判断是否为绘图语句
      return 1;
    },
    followUpOperations(TermWrite, isClear) { // 执行一部分重复的操作
      this.recordInput(TermWrite);
      this.termWrite = '';
      this.isloading = false;
      this.$store.commit('matlab/updateTempArray', TermWrite);
      this.$store.commit('matlab/updateTermArrayPointer');
      this.saveCode = isClear ? '' : this.saveCode;
    },
    async handleMultCommand(termWrite) {
      termWrite = termWrite.replaceAll(/\\\n/g, '');
      const TermArray = termWrite.split('\n');
      for (let i = 0; i < TermArray.length - 1; i++) {
        this.termWrite = TermArray[i];
        this.isloading = true;
        await this.solveCommand(this.termWrite);
      }
      this.termWrite = TermArray[TermArray.length - 1];
      this.$nextTick(() => {
        this.$refs.termInput.focus();
      });
    },
    handleCommand(input) {
      let TermWrite = '';
      if (input === null) { // 页面输入
        const event = window.event || arguments[0];
        if (event.key !== 'Enter' && event.code !== 'Enter' && event.keyCode !== 13) {
          return;
        }
        event.returnValue = false; // 阻止浏览器输入回车后换行
        TermWrite = this.termWrite;
      } else if (typeof (input) === 'string') { // 其他位置调用输入
        TermWrite = input;
        this.termWrite = input;
      } else { // 未知输入
        console.error('未知输入！');
        return;
      }
      this.isloading = true;
      this.$nextTick(() => {
        this.solveCommand(TermWrite);
      });
    },
    async solveCommand(TermWrite) {
      this.saveCode = (this.saveCode === '') ? TermWrite + '\n' : this.saveCode + TermWrite + '\n';
      const indent = TermWrite.length - TermWrite.replace(/^\s*/g, '').length;
      const type = this.checkType(TermWrite);
      if (this.tags.isHandle) {
        if (this.tags.isIndent) { // 需要缩进处理的三种缩进情况，只有处理了缩进的可以放下去
          if (!(TermWrite !== '' && TermWrite.trim() === '')) { // 此时的是对于空行但不是空的处理，空行是直接跳过的
            this.tags.isIndent = false;
            if (indent > this.Indent) {
              this.Indent = indent;
              if ((type >= 0 && type <= 3) || type === 7) {
                const currentType = { type: type, Indent: indent, isTryType: type === 3 };
                this.currentIndent.push(currentType);
                this.tags.isIndent = (type >= 0 && type <= 3);
              } else {
                this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                return;
              }
            } else if (indent < this.Indent) { // 需要缩进但是还比原本的缩进小
              if (TermWrite === '') {
                this.recordInput(TermWrite);
                this.termWrite = '';
                this.isloading = false;
                this.saveCode = '';
                this.tags.isHandle = false;
                this.throwErr('IndentationError', 'expected an indented block');
                this.removeCurrentIndent(0);
                this.Indent = 0;
                this.frontDisplay = 'Tianyan&nbsp;&gt;&nbsp;';
                return;
              } else {
                this.handleErr(TermWrite, 'IndentationError', 'unindent does not match any outer indentation level');
                return;
              }
            } else { // 当前缩进等于应该缩进
              if (TermWrite === '') {
                this.recordInput(TermWrite);
                this.termWrite = '';
                this.isloading = false;
                this.saveCode = '';
                this.tags.isHandle = false;
                this.throwErr('IndentationError', 'expected an indented block');
                this.removeCurrentIndent(0);
                this.Indent = 0;
                this.frontDisplay = 'Tianyan&nbsp;&gt;&nbsp;';
                return;
              } else {
                this.handleErr(TermWrite, 'IndentationError', 'expected an indented block');
                return;
              }
            }
          }
        } else { // 不需要缩进的三种，只有缩进和当前相等的可以放下去
          if (TermWrite.trim() !== '') {
            if (indent > this.Indent) { // 大于情况
              this.handleErr(TermWrite, 'IndentationError', 'unexpected indent');
              return;
            } else if (indent < this.Indent) { // 小于情况
              const index = this.findIndex(indent);
              if (index === -1) {
                this.handleErr(TermWrite, 'IndentationError', 'unindent does not match any outer indentation level');
                return;
              } else {
                if (type === 7 || (type >= 0 && type <= 3)) {
                  if (index === 1) {
                    this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                    return;
                  } else {
                    if (this.currentIndent[index].type === 3) {
                      this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                      return;
                    } else {
                      this.tags.isIndent = (type >= 0 && type <= 3);
                      this.currentIndent[index].isTryType = (type === 3);
                      this.currentIndent[index].type = type;
                      this.removeCurrentIndent(index);
                      this.Indent = indent;
                    }
                  }
                } else if (type === 4) { // elif
                  if (this.currentIndent[index].type === 1 || this.currentIndent[index].type === 4) {
                    this.handleIndent(index, indent, type);
                  } else {
                    this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                    return;
                  }
                } else if (type === 6) { // except
                  if (this.currentIndent[index].type === 3 || this.currentIndent[index].type === 6) {
                    this.handleIndent(index, indent, type);
                  } else {
                    this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                    return;
                  }
                } else if (type === 5) { // else
                  if (this.currentIndent[index].type !== 0 && this.currentIndent[index].type !== 5 && this.currentIndent[index].type !== 7) {
                    this.handleIndent(index, indent, type);
                  } else {
                    this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                    return;
                  }
                } else if (type === 8) {
                  this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                  return;
                } else if (type === 9) {
                  if (this.currentIndent[index].type === 3 || this.currentIndent[index].type === 6 || this.currentIndent[index].type === 5) {
                    if (this.currentIndent[index].isTryType === true) {
                      this.handleIndent(index, indent, type);
                      this.currentIndent[index].isTryType = false;
                    } else {
                      this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                      return;
                    }
                  } else {
                    this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                    return;
                  }
                }
              }
            } else { // 等于indent情况，前面的只能是7，所以就是将对应的存放内容更改一下
              const index = this.findIndex(indent);
              if ((type >= 0 && type <= 3) || type === 7) {
                this.handleIndent(index, indent, type);
                this.currentIndent[index].isTryType = (type === 3);
                this.tags.isIndent = (type >= 0 && type <= 3);
              } else {
                this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
                return;
              }
            }
          }
        }
      } else {
        if (TermWrite === '') { // 没有命令，在python命令行中如果是空命令的话不需要存到历史命令
          this.recordInput();
          this.termWrite = '';
          this.saveCode = '';
          this.isloading = false;
          return;
        }
        if (TermWrite.trim() === '') {
          this.followUpOperations(TermWrite, true);
          return;
        }

        if (TermWrite.trim() === 'help') { // 显示帮助
          this.followUpOperations(TermWrite, true);
          for (const command of this.commands) {
            this.termResult += `    ${this.replace(command.name)}`;
            let Len = 24 - command.name.length;
            while (Len--) this.termResult += ' ';
            this.termResult += `${this.replace(command.describe)}`;
            this.termResult += '\n';
          }
          return;
        }

        if (this.variables !== null) { // 检查变量展示
          for (const variable of this.variables) {
            if (variable['Type'] === 'module' || variable['Type'] === 'function' || variable['Type'] === 'dict' || variable['Type'] === 'set' || variable['Value'].includes('object')) { continue; }
            if (variable.Name === TermWrite.trim()) {
              this.followUpOperations(TermWrite, true);
              this.termResult += '\n';
              this.termResult += `<span style="font-weight:bold;">${this.replace(variable.Name)}&nbsp;=</span>\n`;
              this.termResult += `${this.replace(variable.Value)}\n`;
              this.termResult += '\n';
              return;
            }
          }
        }

        for (const command of this.commands) { // 检查命令，较简单版本，后续可完善更多功能
          if (TermWrite.trim() === command.name) {
            this.followUpOperations(TermWrite, true);
            command.func();
            return;
          }
        }
        if (/^(for\s\w+\sin\s.+|def.+\(.*\)|if.+|while.+|class\s+\w+\s*|try\s*)$/g.test(TermWrite.trim())) {
          if (indent !== 0) {
            this.handleErr(TermWrite, 'IndentationError', 'unexpected indent');
            return;
          } else if (/^(for\s\w+\sin\s.+|def.+\(.*\)|if.+|while.+|class\s+\w+\s*|try\s*):[\s\S]*$/g.test(TermWrite)) {
            this.tags.isHandle = true;
            this.tags.isIndent = true;
            if (this.currentIndent.length === 1) {
              const currentType = { type: type, Indent: indent, isTryType: (/^try\s*:/g.test(TermWrite.trim())) };
              this.currentIndent.push(currentType);
            }
            this.followUpOperations(TermWrite, false);
            this.frontDisplay = 'Tianyan&nbsp;.&nbsp;';
            return;
          } else {
            this.handleErr(TermWrite, 'SyntaxError', 'invalid syntax');
            return;
          }
        }
      }

      // 将输入作为代码执行
      let termCode = 'import pangu\nimport kaitian\nimport numpy as np\n';
      // if (this.variables !== null) { // 将工作区变量加在代码前
      //   const arrayLikeType = ['NDArray', 'MaskedArray', 'Matrix', 'Array of '];
      //   for (const variable of this.variables) {
      //     if (variable['Type'] === 'module' || variable['Type'] === 'function') { continue; }
      //     let isArrayLikeType = false;
      //     for (const Type of arrayLikeType) {
      //       if (variable['Type'].includes(Type)) {
      //         isArrayLikeType = true;
      //       }
      //     }
      //     if (isArrayLikeType) {
      //       let value = variable.Value;
      //       value = value.replace(/\s/g, ',').replace(/\[,/g, '[').replace(/,+/g, ',');
      //       termCode += `${variable.Name} = np.array(${value})\n`;
      //     } else if (variable.Type === 'str') {
      //       termCode += `${variable.Name} = "${variable.Value}"\n`;
      //     } else {
      //       termCode += `${variable.Name} = ${variable.Value}\n`;
      //     }
      //   }
      // }
      // 解析输入，进行相应处理
      let op;
      if (!this.tags.isHandle) { // 没有进入for循环等多行输入情况下，也就是单行执行时
        op = this.checkSentence(TermWrite);
        if (op === 0) {
          termCode += TermWrite.trim();
          this.$store.commit('matlab/updateTempArray', TermWrite);
          this.$store.commit('matlab/updateTermArrayPointer');
        } else if (op === 1) {
          termCode += 'ans = ' + TermWrite.trim();
          termCode += '\nprint(ans)';
          this.$store.commit('matlab/updateTempArray', TermWrite.trim());
          this.$store.commit('matlab/updateTermArrayPointer');
        }
      } else {
        if (/^(for\s\w+\sin\s.+|def.+\(.*\)|if.+|while.+|class\s+\w+\s*|try\s*):[\s\S]*\n\n$/g.test(this.saveCode)) { // 执行多行输入后的所有语句
          termCode += this.saveCode.trim();
          this.saveCode = '';
          this.tags.isHandle = false;
          TermWrite = this.saveCode;
          op = 3;
        } else { // 执行进入缩进内部的语句。
          this.followUpOperations(TermWrite, false);
          return;
        }
      }
      await this.$store.dispatch('matlab/runTerminalCode', { TerminalCode: termCode, TerminalWrite: TermWrite, TerminalOp: op })
        .finally(_ => {
          this.termWrite = '';
          this.isloading = false;
          this.saveCode = '';
          this.removeCurrentIndent(0);
          this.Indent = 0;
          this.frontDisplay = 'Tianyan&nbsp;&gt;&nbsp;';
          this.$nextTick(() => {
            this.$refs.termInput.focus();
          });
        });
    }
  }
};
</script>

<style lang="scss" scoped>
// 终端样式
.terminal {
  text-align: left;
  background: #FAFAFA; // 背景色
  color: #6C7680; // 字体颜色
  font-family: 'Consolas1';
  font-size: 15px;
  white-space: pre-wrap;
  padding: 0px 0px 0px 8px;
  border: none;
  height: 100%;
  width: 100%;
}

// 命令行样式
.commandLine {
  display: flex;
}

// 重写el-input样式
::v-deep .el-textarea__inner {
  background: #FAFAFA; // 背景色
  caret-color: #0A0E14; // 光标颜色
  color: #3CB371; // 字体颜色
  font-family: 'Consolas1';
  font-size: 15px;
  padding: 0px 0px 0px 0px;
  border: none;
}
::v-deep .is-disabled .el-textarea__inner {
  background: #FAFAFA; // 背景色
  caret-color: #0A0E14; // 光标颜色
  color: #3CB371; // 字体颜色
  font-family: 'Consolas1';
  font-size: 15px;
  padding: 0px 0px 0px 0px;
  border: none;
  cursor: text;
}

// 鼠标选中文字颜色
::v-deep ::selection {
  background:#CCDDFF;
}
::v-deep ::-moz-selection {
  background:#CCDDFF;
}
::v-deep ::-webkit-selection {
  background:#CCDDFF;
}

</style>
